<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>模拟vue双向绑定</title>
</head>
<body>
    <div class="">
        简单的双向绑定实现
        <input type="text" id="id-input">
    </div>
    <div id="id-div"></div>
    <div>
        <h1>手写双向绑定(模仿vue2)</h1>
        <div id="app">
            <input type="text" v-model="text">
            {{text}}
        </div>
    </div>
</body>
</html>
<script>
    // 模仿vue2实现的双向绑定
    const defineReactive = function (obj, key, value) {
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            get: function () {
                if (Dep.target) {
                    dep.addSub(Dep.target);
                }
                return value
            },
            set: function (newVal) {
                if (newVal === value) {
                    return;
                }
                value = newVal;
                console.log("新值：" + newVal);

                // 数据改变，通知订阅者
                dep.notify();
            }
        });
    }

    // 观察者函数
    const observe = function (obj, vm) {
        for(let key of Object.keys(obj)) {
            defineReactive(vm, key, obj[key]);
        }
    }

    // 编译函数
    const compile = function (node, vm) {
        let reg = /\{\{(.*)\}\}/; // 匹配 {{ xxx }} 中的xxx
        // 如果是元素节点
        if (node.nodeType === 1) {
            let attr = node.attributes;
            // 解析元素节点的所有属性
            for (let i = 0; i < attr.length; i++) {
                if (attr[i].nodeName === "v-model") {
                    let name = attr[i].nodeValue; // 看看是和那个数据相关
                    node.addEventListener("input", function (e) {
                        vm[name] = e.target.value;
                    });
                    node.value = vm[name];
                    node.removeAttribute("v-model");
                }
            }
        }
        // 如果是文本节点
        if (node.nodeType === 3) {
            if (reg.test(node.nodeValue)) {
                let name = RegExp.$1; // 获取到匹配的字符串
                name = name.trim();
                // node.nodeValue = vm[name]; // 将data的值付给该node

                new Watcher(vm, node, name); // 不直接通过赋值操作，而是通过绑定一个订阅者实现
            }
        }
    }

    // 观察者构造函数
    const Watcher = function(vm, node, name) {
        Dep.target = this; // Dep.target 是一个全局变量
        this.vm = vm;
        this.node = node;
        this.name = name;
        this.update();
        Dep.target = null;
    }
    
    Watcher.prototype = {
        update(){
            this.get();
            this.node.nodeValue = this.value;
        },
        get(){
            this.value = this.vm[this.name];
        }
    }

    // dep构造函数
    const Dep = function () {
        this.subs = [];
    }
    Dep.prototype = {
        addSub(sub){
            this.subs.push(sub);
        },
        notify(){
            this.subs.forEach(function (sub) {
                sub.update();
            })
        }
    }
    
    const nodeToFragment = function (node, vm) {
        let fragment = document.createDocumentFragment(); // 创建节点
        let child;
        while ((child = node.firstChild)) {
            compile(child, vm);
            fragment.appendChild(child);
        }
        return fragment;
    }

    // vue 构造函数
    const MyVue = function (options){
        this.data = options.data;
        let data = this.data;
        observe(data, this);
        let id = options.el;
        let dom = nodeToFragment(document.getElementById(id), this);
        // 处理完所有dom节点后，重新将内容添加回去
        document.getElementById(id).appendChild(dom);
    }

    let vm = new MyVue({
        el: "app",
        data: {
            text: "你好"
        }
    });
    


</script>
<script>
    // 简单的双向绑定实现
    let user = {}
    let input = document.querySelector("#id-input");
    let text = document.querySelector("#id-div");

    // 数据到视图 model => view
    Object.defineProperty(user, "name", {
        get: function () {
            console.log("获取user的name");
            return this._name;
        },
        set: function (val) {
            console.log("修改user的name");
            this._name = val;
            text.textContent = this._name;
        }
    });

    // 视图到数据模型 view => model
    input.addEventListener("input", function (e) {
        user.name = e.target.value;
        console.log("===========================");
        console.log("监听器中将user的name设置为：" + user.name);
    });


</script>